package com.atguigu.gmall.gateway.filter;

import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.Jsons;
import com.atguigu.gmall.gateway.properties.AuthUrlProperties;
import com.atguigu.gmall.user.entity.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @author:ahang
 * @create:2022-10-18 22:18
 * @Description:
 */
@Slf4j
@Component
public class UserAuthFilter implements GlobalFilter {
    @Autowired
    AuthUrlProperties authUrlProperties;

    @Autowired
    StringRedisTemplate redisTemplate;

    AntPathMatcher matcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();
        //1.如果是静态资源,直接放行
        List<String> noauthurl =  authUrlProperties.getNoauthurl();
        for (String pattern : noauthurl) {
            //判断当前请求是否满足这种格式
            if(matcher.match(pattern,path)){
                return chain.filter(exchange);
            }
        }

        //2.任何情况下都不能访问的路径
        List<String> denyurl = authUrlProperties.getDenyurl();
        for (String innerPattern : denyurl) {
            if(matcher.match(innerPattern,path)){
                //拒绝访问,直接响应一个json
                Result<String> result = Result.build("", ResultCodeEnum.PERMISSION);
                return responseJson(exchange,result);
            }
        }


        //3.需要登录才能访问
        for (String pattern : authUrlProperties.getLoginauth()) {
            if(matcher.match(pattern,path)){
                //先拿到用户传来的token
                String token = getUserToken(exchange);
                if(!StringUtils.isEmpty(token)){
                    //说明登录了
                        //-是真令牌还是假令牌
                    UserInfo info = getUserInfoByToken(token);
                    if(info == null){
                        //假令牌,打回到登录页
                        return locationToUrl(exchange,authUrlProperties.getLoginPage());
                    }
                        //真令牌直接放行,给request中加一个userid的请求头再放行
                    return userIdThrought(chain,exchange,info);
                }else{
                    //没登录,跳转到登录页
                    return locationToUrl(exchange,authUrlProperties.getLoginPage());
                }
            }
        }

        //4.对于普通请求:带token就验证并透传id,没带就不透传
        String userToken = getUserToken(exchange);
        if(!StringUtils.isEmpty(userToken)){
            UserInfo info = getUserInfoByToken(userToken);
            if(info != null){
                return userIdThrought(chain,exchange,info);
            }else{
                return locationToUrl(exchange,authUrlProperties.getLoginPage());
            }
        }else{
            //不带令牌的也需要进行临时id的透传
            return userTempIdThrought(chain,exchange);
        }
    }

    /**
     * 透传临时id
     * @param chain
     * @param exchange
     * @return
     */
    private Mono<Void> userTempIdThrought(GatewayFilterChain chain, ServerWebExchange exchange) {
        String tempId = getUserTempId(exchange);
        //变异一个新请求--相当于克隆
        ServerHttpRequest newRequest = exchange.getRequest()
                .mutate()
                .header(RedisConst.User_TEMP_ID_HEADER,tempId)
                .build();
        //也变异一个新的exchange对象
        ServerWebExchange newExchange = exchange.mutate()
                .request(newRequest)
                .response(exchange.getResponse())
                .build();

        return chain.filter(newExchange );

    }

    /**
     * 给浏览器写一个json
     * @param exchange
     * @param result
     * @return
     */
    private Mono<Void> responseJson(ServerWebExchange exchange, Result<String> result) {
        String jsonStr = Jsons.tostr(result);
        //1.拿到响应写数据出去
        ServerHttpResponse response = exchange.getResponse();
        //说明响应的类型,浏览器就会将编码调整为对应的编码进行解析
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
        //响应式编程要将数据保存到缓存区,然后刷出去
        //2.创建写出去的数据
        DataBuffer buffer = response.bufferFactory().wrap(jsonStr.getBytes(StandardCharsets.UTF_8));
        //3.写数据
        return response.writeWith(Mono.just(buffer));
    }

    /**
     * 透传id并放行请求
     * @param exchange
     * @return
     */
    private Mono<Void> userIdThrought(GatewayFilterChain chain
                        ,ServerWebExchange exchange,UserInfo info) {
        Long userId = info.getId();
        String tempId = getUserTempId(exchange);
        //请求是只读不能修改
//        exchange.getRequest().getHeaders()
//                .add(RedisConst.User_ID_HEADER,userId+"");
        //变异一个新请求--相当于克隆
        ServerHttpRequest newRequest = exchange.getRequest()
                .mutate()
                .header(RedisConst.User_ID_HEADER, userId.toString())
                .header(RedisConst.User_TEMP_ID_HEADER,tempId)
                .build();
        //也变异一个新的exchange对象
        ServerWebExchange newExchange = exchange.mutate()
                .request(newRequest)
                .response(exchange.getResponse())
                .build();

        return chain.filter(newExchange );
    }

    /**
     * 获取临时id,跟获取token的方式是一样的
     * @param exchange
     * @return
     */
    private String getUserTempId(ServerWebExchange exchange) {
        ServerHttpRequest request = exchange.getRequest();
        String tempId = "";
        //因为token的位置不同,先去请求头中的cookie字段获取值
        HttpCookie cookie = request.getCookies().getFirst("userTempId");
        if(cookie != null){
            tempId = cookie.getValue();
        }else{
            //再去请求头中获取token字段
            tempId = request.getHeaders().getFirst("userTempId");
        }
        return tempId;
    }

    /**
     * token去redis中查找用户信息
     * @param token
     * @return
     */
    private UserInfo getUserInfoByToken(String token) {
        String json = redisTemplate.opsForValue().get(RedisConst.USER_LOGIN + token);
        if(!StringUtils.isEmpty(json)){
            UserInfo userInfo = Jsons.toObject(json, UserInfo.class);
            return userInfo;
        }
        return null;
    }

    /**
     * 跳转登录页
     * @param exchange
     * @param loginPage
     * @return
     */
    private Mono<Void> locationToUrl(ServerWebExchange exchange, String loginPage) {
        String path = exchange.getRequest().getURI().toString();
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.FOUND);
        //跳转到登录页之前的位置
        response.getHeaders().add("Location",loginPage+"?originUrl="+path );

        //命令浏览器清空一个假的cookie 服务器只需要发一个同名cookie,maxAge=0立即删除
        ResponseCookie cookie = ResponseCookie.from("token", "aa").domain(".gmall.com").maxAge(0).build();
        response.addCookie(cookie);

        //响应结束
        return response.setComplete();
    }

    /**
     * 获取用户令牌
     * @param exchange
     * @return
     */
    private String getUserToken(ServerWebExchange exchange) {
        ServerHttpRequest request = exchange.getRequest();
        String userToken = "";
        //因为token的位置不同,先去请求头中的cookie字段获取值
        HttpCookie cookie = request.getCookies().getFirst("token");
        if(cookie != null){
            userToken = cookie.getValue();
        }else{
            //再去请求头中获取token字段
            userToken = request.getHeaders().getFirst("token");
        }
        return userToken;
    }
}
